package com.dhecp.project.flowable.service.impl;

import com.dhecp.common.core.domain.AjaxResult;
import com.dhecp.common.core.page.PageDomain;
import com.dhecp.common.core.page.TableSupport;
import com.dhecp.common.utils.MessageUtils;
import com.dhecp.common.utils.StringUtils;
import com.dhecp.common.utils.TypeUtils;
import com.dhecp.project.flowable.domain.ActProcessDefinition;
import com.dhecp.project.flowable.extend.CustomBpmnJsonConverter;
import com.dhecp.project.flowable.extend.CustomProcessDiagramGenerator;
import com.dhecp.project.flowable.mapper.SysProcessInstanceMapper;
import com.dhecp.project.flowable.service.ActProcessDefinitionService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.pagehelper.Page;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.editor.constants.ModelDataJsonConstants;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.repository.*;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.ProcessDiagramGenerator;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

/**
 * @ClassName ActProcessDefinitionServiceImpl
 * @Description TODO
 * @Author alula
 * @Date 2019-11-26 11:57
 */
@Service
public class ActProcessDefinitionServiceImpl implements ActProcessDefinitionService {

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private SysProcessInstanceMapper sysProcessInstanceMapper;

    @Override
    public Page<ActProcessDefinition> queryProcessDefinitionList(Map<String, Object> params) {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();
        Page<ActProcessDefinition> list = new Page<ActProcessDefinition>();
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        if (pageDomain.getOrderByColumn().contains("deploymentTime") && pageDomain.getOrderBy().contains("desc")){
            processDefinitionQuery.orderByProcessDefinitionVersion().desc().orderByDeploymentId().desc();
        }
        if (pageDomain.getOrderByColumn().contains("deploymentTime") && pageDomain.getOrderBy().contains("asc")){
            processDefinitionQuery.orderByProcessDefinitionVersion().asc().orderByDeploymentId().asc();
        }
        if (pageDomain.getOrderByColumn().contains("key") && pageDomain.getOrderBy().contains("desc")){
            processDefinitionQuery.orderByProcessDefinitionKey().desc();
        }
        if (pageDomain.getOrderByColumn().contains("key") && pageDomain.getOrderBy().contains("asc")){
            processDefinitionQuery.orderByProcessDefinitionKey().asc();
        }
        if (pageDomain.getOrderByColumn().contains("name") && pageDomain.getOrderBy().contains("desc")){
            processDefinitionQuery.orderByProcessDefinitionName().desc();
        }
        if (pageDomain.getOrderByColumn().contains("name") && pageDomain.getOrderBy().contains("asc")){
            processDefinitionQuery.orderByProcessDefinitionName().asc();
        }
        if (pageDomain.getOrderByColumn().contains("category") && pageDomain.getOrderBy().contains("desc")){
            processDefinitionQuery.orderByProcessDefinitionCategory().desc();
        }
        if (pageDomain.getOrderByColumn().contains("category") && pageDomain.getOrderBy().contains("asc")){
            processDefinitionQuery.orderByProcessDefinitionCategory().asc();
        }
        if (pageDomain.getOrderByColumn().contains("version") && pageDomain.getOrderBy().contains("desc")){
            processDefinitionQuery.orderByProcessDefinitionVersion().desc();
        }
        if (pageDomain.getOrderByColumn().contains("version") && pageDomain.getOrderBy().contains("asc")){
            processDefinitionQuery.orderByProcessDefinitionVersion().asc();
        }
        if (params.containsKey("key") && !StringUtils.isEmpty(params.get("key").toString())){
            processDefinitionQuery.processDefinitionKeyLike("%" + params.get("key").toString() + "%");
        }
        if (params.containsKey("name") && !StringUtils.isEmpty(params.get("name").toString())){
            processDefinitionQuery.processDefinitionNameLike("%" + params.get("name").toString() + "%");
        }
        if (params.containsKey("category") && !StringUtils.isEmpty(params.get("category").toString())){
            processDefinitionQuery.processDefinitionCategoryLike("%" + params.get("category").toString() + "%");
        }
        if (params.containsKey("status") && !StringUtils.isEmpty(params.get("status").toString())){
            int intStatus = TypeUtils.getSafeInt(params.get("status").toString(), -1);
            if(intStatus ==0){
                processDefinitionQuery.suspended();
            }else if(intStatus == 1) {
                processDefinitionQuery.active();
            }
        }
        if (params.containsKey("id") && !StringUtils.isEmpty(params.get("id").toString())){
            processDefinitionQuery.processDefinitionId(params.get("id").toString());
        }
        if (params.containsKey("version") && !StringUtils.isEmpty(params.get("version").toString())){
            int intVersion = TypeUtils.getSafeInt(params.get("version").toString(), 0);
            if(intVersion > 0) {
                processDefinitionQuery.processDefinitionVersion(intVersion);
            }else{
                processDefinitionQuery.processDefinitionVersion(Integer.MAX_VALUE);
            }
        }
        List<org.flowable.engine.repository.ProcessDefinition> processDefinitionList;
        if (pageNum != null && pageSize != null) {
            processDefinitionList = processDefinitionQuery.listPage((pageNum - 1) * pageSize, pageSize);
            list.setTotal(processDefinitionQuery.count());
            list.setPageNum(pageNum);
            list.setPageSize(pageSize);
        } else {
            processDefinitionList = processDefinitionQuery.list();
        }
        for (org.flowable.engine.repository.ProcessDefinition definition: processDefinitionList) {
            ActProcessDefinition entity = new ActProcessDefinition();
            entity.setId(definition.getId());
            entity.setKey(definition.getKey());
            entity.setName(definition.getName());
            entity.setCategory(definition.getCategory());
            entity.setVersion(definition.getVersion());
            entity.setDescription(definition.getDescription());
            entity.setDeploymentId(definition.getDeploymentId());
            entity.setResourceName(definition.getResourceName());
            entity.setDiagramResourceName(definition.getDiagramResourceName());
            entity.setStatus((definition.isSuspended()) ? 0 :1);
            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(definition.getDeploymentId()).singleResult();
            entity.setDeploymentTime(deployment.getDeploymentTime());
            list.add(entity);
        }
        return list;
    }

    @Override
    @Transactional
    public AjaxResult deployProcessDefinition(InputStream fileInputStream, String fileName) {
        if (!StringUtils.isEmpty(fileName)) {
            String extension = FilenameUtils.getExtension(fileName);
            String baseName = FilenameUtils.getBaseName(fileName);
            if ("zip".equals(extension) || "bar".equals(extension)) {
                ZipInputStream zipInputStream = new ZipInputStream(fileInputStream);
                repositoryService.createDeployment().name(baseName).addZipInputStream(zipInputStream).deploy();
            }  else if (fileName.indexOf("bpmn20.xml") != -1) {
                repositoryService.createDeployment().name(baseName).addInputStream(fileName, fileInputStream).deploy();
            } else if ("bpmn".equals(extension)) {
                repositoryService.createDeployment().name(baseName).addInputStream(baseName + ".bpmn20.xml", fileInputStream).deploy();
            }else {
                AjaxResult.error(MessageUtils.message("flowable.definition.error.flowfiletype", extension));
            }
        }
        return AjaxResult.success(MessageUtils.message("module.success.issuccess"));
    }

    @Override
    @Transactional
    public AjaxResult deleteProcessDeploymentById(String id, String depId) {
        if (StringUtils.isEmpty(id)) {
            return AjaxResult.error(MessageUtils.message("module.error.paramfail", id));
        }
        if (StringUtils.isEmpty(depId)) {
            return AjaxResult.error(MessageUtils.message("module.error.paramfail", depId));
        }
        List<ProcessInstance> instanceList = runtimeService.createProcessInstanceQuery().deploymentId(depId).list(); // 跳过存在流程实例的流程定义
        if (!CollectionUtils.isEmpty(instanceList)) {
            return AjaxResult.error(MessageUtils.message("flowable.definition.error.deletedefinitionfail"));
        };
        int defCount =  sysProcessInstanceMapper.findSysProcessInstanceByProcDefIdToCount(id);
        if(defCount > 0){
            return AjaxResult.error(MessageUtils.message("flowable.definition.error.deletedefinitionfail"));
        }
        repositoryService.deleteDeployment(depId, true); // true 表示级联删除引用，比如 act_ru_execution 数据
        return AjaxResult.success(MessageUtils.message("module.success.issuccess"));
    }

    @Override
    @Transactional
    public AjaxResult convertProcessDefinitionToModel(String processId) throws Exception{
        if (StringUtils.isEmpty(processId)) {
            return AjaxResult.error(MessageUtils.message("module.error.paramfail", processId));
        }
       org.flowable.engine.repository.ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
               .processDefinitionId(processId).singleResult();
        InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
                processDefinition.getResourceName());
        XMLInputFactory xif = XMLInputFactory.newInstance();
        InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");
        XMLStreamReader xtr = xif.createXMLStreamReader(in);
        BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
        bpmnModel.setTargetNamespace(processDefinition.getCategory());
        BpmnJsonConverter converter = new CustomBpmnJsonConverter();
        ObjectNode modelNode = converter.convertToJson(bpmnModel);
        Model modelData = repositoryService.newModel();
        modelData.setKey(processDefinition.getKey());
        modelData.setName(processDefinition.getName());
        modelData.setCategory(processDefinition.getCategory());
        modelData.setDeploymentId(processDefinition.getDeploymentId());
        modelData.setVersion(Integer.parseInt(String.valueOf(repositoryService.createModelQuery().modelKey(modelData.getKey()).count() + 1)));

        ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
        modelObjectNode.put("key", processDefinition.getKey());
        modelNode.put(ModelDataJsonConstants.MODEL_ID, modelData.getId());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, processDefinition.getName());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, modelData.getVersion());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, processDefinition.getDescription());
        modelData.setMetaInfo(modelObjectNode.toString());

        repositoryService.saveModel(modelData);
        repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes("utf-8"));
        return AjaxResult.success(MessageUtils.message("module.success.issuccess"));
    }

    @Override
    public AjaxResult updateProcessDefinitionStatus(String proDefId, int status) {
        if (StringUtils.isEmpty(proDefId)) {
            return AjaxResult.error(MessageUtils.message("module.error.paramfail", proDefId));
        }
        if(status == 1){
            repositoryService.activateProcessDefinitionById(proDefId, true, null);
        }else if(status == 0){
            repositoryService.suspendProcessDefinitionById(proDefId, true, null);
        }
        return AjaxResult.success(MessageUtils.message("module.success.issuccess"));
    }

    @Override
    public ProcessDefinition queryActProcessDefinitionByKey(String proDefKey) {
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionKey(proDefKey).orderByProcessDefinitionVersion().desc().list();
        if(list != null && list.size() > 0){
            return  list.get(0);
        }
        return null;
    }

    @Override
    public ProcessDefinition queryActProcessDefinitionById(String proDefId) {
        return  repositoryService.createProcessDefinitionQuery().processDefinitionId(proDefId).singleResult();
    }

    @Override
    public InputStream getProcessDefinitionImage(String proDefId, String deploymentId, String resourceName) {
        InputStream imageStream = null;
        if (StringUtils.isEmpty(deploymentId)){
            return  imageStream;
        }
        try {
            BpmnModel bpmnModel = repositoryService.getBpmnModel(proDefId);
            // 获取已流经的流程线，需要高亮显示高亮流程已发生流转的线id集合
            List<String> highLightedFlowIds = new ArrayList<>();
            List<String> highLightedFlowableIdList = new ArrayList<>();
            List<String> runningFlowableIdList = new ArrayList<>();

            ProcessDiagramGenerator processDiagramGenerator = null;
            processDiagramGenerator = new CustomProcessDiagramGenerator();
            //使用默认配置获得流程图表生成器，并生成追踪图片字符流
            /*imageStream = processDiagramGenerator.generateDiagram(bpmnModel, "png",
                    highLightedFlowableIdList, highLightedFlowIds,
                    "黑体", "黑体", "黑体",
                    null, 2.0, true);*/
            imageStream = ((CustomProcessDiagramGenerator) processDiagramGenerator)
                    .generateDiagramCustom(bpmnModel, "png",
                            highLightedFlowableIdList, runningFlowableIdList, highLightedFlowIds,
                            "黑体", "黑体", "黑体",
                            null, 2.0);
            //imageStream =  repositoryService.getResourceAsStream(deploymentId, resourceName);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
        return  imageStream;
    }
}
